package com.yc.framework.aspectj;

import com.alibaba.fastjson2.JSON;
import com.yc.common.annotation.RateLimiter;
import com.yc.common.exceptions.ServiceException;
import com.yc.common.constant.HttpStatus;
import com.yc.system.domain.SysLog;
import com.yc.common.core.domain.model.LoginUser;
import com.yc.framework.web.service.TokenService;
import com.yc.system.service.IpLimitService;
import com.yc.common.utils.WebUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class RateLimitAspect {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private IpLimitService ipLimitService;

    // 缓存每个接口对应的 RateLimiter 实例，保证各自互不干扰
    // 修改为两级Map：方法名 -> IP地址 -> RateLimiter
    private Map<String, Map<String, com.google.common.util.concurrent.RateLimiter>> methodIpLimiters = new ConcurrentHashMap<>();

    // 记录IP访问次数的Map
    private Map<String, Integer> ipViolationCount = new ConcurrentHashMap<>();

    // 记录被禁止的IP
    private Map<String, Long> bannedIps = new ConcurrentHashMap<>();

    // IP禁止时长（分钟）
    private static final int BAN_MINUTES = 30;

    // 触发禁止的违规次数
    private static final int MAX_VIOLATION_COUNT = 7;

    /**
     * 对标记了 @RateLimit 的方法进行切面控制
     */
    @Around("@annotation(com.yc.common.annotation.RateLimiter)")
    public Object aroundRateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        String ipAddress = WebUtil.getIpAddr();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RateLimiter rateLimitAnnotation = method.getAnnotation(RateLimiter.class);

        // 获取当前请求的IP地址
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return joinPoint.proceed();
        }

        // 检查IP是否被禁止，被禁止的IP直接拒绝，不记录日志
        if (isBanned(ipAddress)) {
            throw new ServiceException("您的IP已被临时禁止访问，请稍后再试", HttpStatus.FORBIDDEN);
        }

        // 构造限流键：全限定类名+方法名
        String methodKey = joinPoint.getTarget().getClass().getName() + "." + method.getName();

        // 获取该方法的IP限流Map
        Map<String, com.google.common.util.concurrent.RateLimiter> ipLimiters = methodIpLimiters.computeIfAbsent(methodKey, k -> new ConcurrentHashMap<>());

        // 获取该IP的限流器
        com.google.common.util.concurrent.RateLimiter rateLimiter = ipLimiters.computeIfAbsent(ipAddress,
                k -> com.google.common.util.concurrent.RateLimiter.create(rateLimitAnnotation.permitsPerSecond()));

        // 如果获取令牌失败，则记录限流失败日志
        if (!rateLimiter.tryAcquire()) {
            // 记录违规次数
            int violations = ipViolationCount.getOrDefault(ipAddress, 0) + 1;
            ipViolationCount.put(ipAddress, violations);

            // 检查是否需要禁止该IP
            if (violations >= MAX_VIOLATION_COUNT) {
                banIp(ipAddress);

                // 如果用户已登录，也禁止用户账号
                LoginUser loginUser = tokenService.getLoginUser();
                if (loginUser != null) {
                    ipLimitService.banUser(loginUser.getUser().getId());
                }
            }

            throw new ServiceException("您的IP已被临时禁止访问，请稍后再试", HttpStatus.FORBIDDEN);
        }

        // 重置违规计数（如果之前有违规记录）
        if (ipViolationCount.containsKey(ipAddress)) {
            ipViolationCount.put(ipAddress, 0);
        }

        // 请求成功通过限流，直接放行
        return joinPoint.proceed();
    }

    /**
     * 检查IP是否被禁止
     */
    public boolean isBanned(String ipAddress) {
        Long banTime = bannedIps.get(ipAddress);
        if (banTime == null) {
            return false;
        }

        // 检查禁止时间是否已过
        if (System.currentTimeMillis() > banTime) {
            bannedIps.remove(ipAddress);
            return false;
        }

        return true;
    }

    /**
     * 禁止IP访问
     */
    private void banIp(String ipAddress) {
        // 设置禁止时间为当前时间 + BAN_MINUTES 分钟
        long banUntil = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(BAN_MINUTES);
        bannedIps.put(ipAddress, banUntil);

        // 重置违规计数
        ipViolationCount.remove(ipAddress);
    }

    /**
     * 构建公共日志对象
     */
    private SysLog buildCommonSysLog(ProceedingJoinPoint joinPoint, long costTime) {
        SysLog sysLog = new SysLog();
        sysLog.setCostTime(costTime);
        sysLog.setCreateTime(new Date());

        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            sysLog.setRequestMethod(request.getMethod());
            sysLog.setRequestUrl(WebUtil.getRequestUrl());
            sysLog.setIp(WebUtil.getIpAddr());
        }

        // 获取当前用户信息（如果存在）
        LoginUser loginUser = tokenService.getLoginUser();
        if (loginUser != null) {
            sysLog.setUserId(loginUser.getUser().getId());
            sysLog.setUsername(loginUser.getUser().getUserName());
        }

        // 设置调用方法信息
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
        sysLog.setMethod(className + "." + methodName + "()");

        return sysLog;
    }

    /**
     * 获取请求参数，避免记录 HttpServletRequest、HttpServletResponse 等不可序列化对象
     */
    private String getRequestParams(ProceedingJoinPoint joinPoint) {
        String params = "";
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            try {
                Object[] filteredArgs = new Object[args.length];
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof HttpServletRequest
                            || args[i] instanceof HttpServletResponse) {
                        filteredArgs[i] = "[不可序列化类型]";
                    } else {
                        filteredArgs[i] = args[i];
                    }
                }
                params = JSON.toJSONString(filteredArgs);
                if (params.length() > 2000) {
                    params = params.substring(0, 2000) + "...";
                }
            } catch (Exception e) {
                params = "[参数序列化异常]";
            }
        }
        return params;
    }
}